package com.gitee.loyo.utils;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gitee.loyo.Request;
import com.gitee.loyo.Result;
import com.gitee.loyo.TokenManager;
import com.gitee.loyo.config.properties.ChargeProperties;
import com.gitee.loyo.config.properties.ChargeQueryProperty;
import com.gitee.loyo.data.Token;
import com.gitee.loyo.err.QueryTokenFailureException;
import com.gitee.loyo.post.QueryTokenPost;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@RequiredArgsConstructor
public class RedisTokenManager implements TokenManager {
    private final String KEY_PREFIX = "__TCEC:Token:";
    protected final ChargeProperties chargeProperties;
    protected final OkHttpHelper okHttpHelper;
    @Resource
    RedisTemplate<String, Object> redisTemplate;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public Token getToken(String operatorKey) {
        try {
            return (Token) redisTemplate.opsForValue().get(KEY_PREFIX + operatorKey);
        } catch (Exception e) {
            log.error("getToken {} error", operatorKey, e);
            return null;
        }
    }

    @Override
    public void saveToken(Token token, String operatorKey) {
        redisTemplate.opsForValue().set(KEY_PREFIX + operatorKey, token, Duration.ofSeconds(token.getTokenAvailableTime()));
    }

    @SneakyThrows
    @Override
    public Token queryToken(String operatorKey) {
        ChargeQueryProperty queryProperty = chargeProperties.getQueryProperty(operatorKey);
        String url = builderUrl(queryProperty.getUrl(), "query_token");
        QueryTokenPost post = new QueryTokenPost();
        post.setOperatorId(queryProperty.getOperatorId());
        post.setOperatorSecret(queryProperty.getOperatorSecret());
        Request<QueryTokenPost> req = new Request<>(queryProperty.getOperatorId(), post);
        log.info("query_token requestBody unencrypted:{}", objectMapper.writeValueAsString(req));
        Result<Token> tokenResult = (Result<Token>) okHttpHelper.post(url, req, null,
                new TypeReference<Result<Token>>() {
                }.getType(), queryProperty);
        log.info("query_token responseBody undecrypted:{}", objectMapper.writeValueAsString(tokenResult));
        if (tokenResult.getRet() != 0 || tokenResult.getData() == null) {
            log.error("query_token error, Ret: " + tokenResult.getRet()
                    + ", Msg: " + tokenResult.getMsg());
            throw new QueryTokenFailureException(tokenResult);
        }
        return tokenResult.getData();
    }

    @SneakyThrows
    @Override
    public void initTokens() {
        Set<String> operatorKeys = chargeProperties.getOperatorKeys();
        for (String operatorKey : operatorKeys) {
            Token newToken = queryToken(operatorKey);
            saveToken(newToken, operatorKey);
            log.info("成功缓存{}的token！", operatorKey);
        }
    }

    @Scheduled(fixedDelay = 15 * 60 * 1000)
    protected void checkTokenEveryMinute() {
        for (String operatorKey : chargeProperties.getOperatorKeys()) {
            try {
                Long tokenExpire = redisTemplate.getExpire(KEY_PREFIX + operatorKey, TimeUnit.SECONDS);
                if (tokenExpire <= 15 * 60) {
                    Token newToken = queryToken(operatorKey);
                    saveToken(newToken, operatorKey);
                }
            } catch (Exception e) {
                log.error("checkTokenEveryQuarter {} error", operatorKey, e);
            }
        }

    }

    private String builderUrl(String baseUrl, String apiName) {
        return baseUrl.endsWith("/") ? baseUrl + apiName
                : baseUrl + "/" + apiName;
    }
}
